package model;

import java.util.Collection;
import java.util.Vector;

/**
 * The Window class  represents a window which notifies all Windows below it if it changes
 * or is disposed and reacts to events of all Windows above it respectively.
 *
 */
public class Window extends RectangularArea implements Observer{
	
	private static int nextWindowNumber = 0;

	private static final int InitialWidth = 200;
	private static final int InitialHeight = 100;
	private static final int Origin = 0;
	private static final Point getInitialPosition(){
		return new Point(Origin,Origin);
	}

	final int number;
	private boolean open;
	Collection<Window> aboveMe;
	private WindowState state;
	
	public WindowState getState() {
		return state;
	}

	private int getNumber() {
		return this.number;
	}
	
	protected void setState(final WindowState state) {
		this.state = state;
	}
	
	/**
	 * Constructor of Window. 
	 */
	public Window(){
		super(getInitialPosition(), InitialWidth, InitialHeight);
		this.number = nextWindowNumber++;
		this.setOpen(true);
		this.setAboveMe(new Vector<Window>());
		this.setState(new VisiblePartsNotCachedState(this));
	}
	
	protected boolean isOpen() {
		return open;
	}
	
	/**
	 * Sets whether this Window is open or not.
	 * Notifies all Windows below this Window.
	 * @param open is if this Window should open.
	 */
	public void setOpen(final boolean open) {
		this.open = open;
		this.notifyObservers(new WindowChangedEvent(this));
		System.out.println("SetOpen(" + open + "): " + this);
	}
	
	@Override
	public void move(final int deltaX, final int deltaY){
		super.move(deltaX, deltaY);
		this.notifyObservers(new WindowChangedEvent(this));
		System.out.println("Move: " + this);
	}
	
	@Override
	public void resize(final int width, final int height) throws NegativeLengthException{
		super.resize(width, height);
		this.notifyObservers(new WindowChangedEvent(this));
		System.out.println("Resize: " + this);
	}

	@Override
	public String toString(){
		return "WINDOW " + this.getNumber() + " : " + super.toString() + " " + (this.isOpen() ? "OPEN" : "CLOSED");
	}
	
	
	@Override
	public boolean isIn(final RectangularArea area) {
		return  area.getLeftUpperCorner().isIn(this)	&&
				area.getLeftLowerCorner().isIn(this)	&&
				area.getRightUpperCorner().isIn(this)	&&
				area.getRightLowerCorner().isIn(this);
	}
	
	public Collection<Window> getAboveMe() {
		return aboveMe;
	}
	
	private void setAboveMe(final Collection<Window> aboveMe) {
		this.aboveMe = aboveMe;
	}
	
	/**
	 * Sets the provided Window as the new top Window.
	 * @param window is the new top Window. 
	 */
	public void setAsTop(final Window window) {
		if (this.equals(window)) {
			this.getAboveMe().clear();
		} else {
			if (!this.getAboveMe().contains(window)){
				this.newTop(window);
			}
		}
	}

	/**
	 * Adds a new Window to the collection of Windows that are above this Window.
	 * @param window is the new Window.
	 */
	private void newTop(final Window window) {
		this.getAboveMe().add(window);
		window.register(this);
		this.deregister(window);
		this.setState(new VisiblePartsNotCachedState(this));
	}

	/**
	 * Notifies all Windows below this Window that this Window is about to get disposed.
	 */
	public void dispose() {
		this.notifyObservers(new WindowDisposedEvent(this));
		System.out.println("Dispose: " + this);
	}
	
	/**
	 * Provides a {@link RectangularPartCollection} with all RectangularParts that 
	 * are not covered by the {@link Window}s above this Window.
	 * @return a {@link RectangularPartCollection} with all RectangularParts that 
	 * are not covered by the {@link Window}s above this Window.
	 */
	public RectangularPartCollection getVisibleContext(){
		return this.getState().getVisibleParts();
	}
	
	@Override
	public void update(final WindowEvent event) {
		event.accept(new WindowEventVisitor() {		
			@Override
			public void handleWindowDisposedEvent(final WindowDisposedEvent event) {
				event.getWindow().deregister(Window.this);
				Window.this.getAboveMe().remove(event.getWindow());
				Window.this.setState(new VisiblePartsNotCachedState(Window.this));
			}
			
			@Override
			public void handleWindowChangedEvent(final WindowChangedEvent event) {
				Window.this.setState(new VisiblePartsNotCachedState(Window.this));
			}
		});		
	}
}